package prac5;

import java.io.BufferedReader;
import java.io.InputStreamReader;


/**
 * Part of a prac using linked lists to implement a simple game.
 * 
 * @author			Menghong Li
 * @author			Ian Moriarty
 * @modified		Menghong Li & Ian Moriarty (August 2011) - Expanded to meet the requirements of Prac 5
 * @since			21th August 2011
 * @input			{@link #main(String[])}: command to execute
 * @input			{@link #LinkedFighterStack chooseArmy(String name, BufferedReader keyboard)}: reads from screen
 * @output			{@link #main(String[])}: command response
 * @errorHandling	Exception: when the user enters invalid input
 */
public class Prac5 {
	/**
	 * A simple command-line driven game.
	 * 
	 * @param		args command-line arguments (not used)
	 * @pre			none
	 * @complexity	Best and Worst case: O(C + D + G)
	 * 				where C is the complexity of chooseArmy for Player 1's army
	 * 					  D is the complexity of chooseArmy for Player 2's army
	 * 					  G is the complexity of gladiatorialCombat for the armies player1 and player2
	 */
	public static void main(String[] args) {
		BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));
		LinkedFighterStack player1 = new LinkedFighterStack();
		LinkedFighterStack player2 = new LinkedFighterStack();
		String player1Name = "Player 1";
		String player2Name = "Player 2";
		int result;
		
		player1 = chooseArmy(player1Name, keyboard);
		player2 = chooseArmy(player2Name, keyboard);
		
		result = gladiatorialCombat(player1, player2);
		
		switch(result) {
			case 0:		System.out.println("The battle was a draw!");			break;
			case 1:		System.out.println(player1Name + " won the battle!");	break;
			case 2:		System.out.println(player2Name + " won the battle!");	break;
			default:	System.out.println("Error: Invalid battle result.");
		}
		System.out.println("Thank you for playing!");
	}
	
	/**
	 * Implements a linked fighter stack by receives the name of the player and allows that player to choose the army.
	 * 
	 * @param		name The player's name
	 * @param		keyboard The IO object
	 * @return		A stack containing the inputted army
	 * @post		The stack contains the inputted army
	 * @complexity	best and worst case O(N + C + A + S)
	 *              where N is the number of times the user attempts to give the input
	 *              	  C is the number of cavalry units, 
	 *                    A is the number of archer units and 
	 *                    S is the number of soldier units
	 * @throws		Exception when invalid input is entered (handled)
	 */
	public static LinkedFighterStack chooseArmy(String name, BufferedReader keyboard) {
		final int MAX_ARMY_COST = 30;
		LinkedFighterStack army = new LinkedFighterStack();
		int S, A, C;
		
		try {
			// Get the player's input for what they want in the army
			System.out.println(name + " choose your army as S A C");
			System.out.println("where S is the number of soldiers (cost $" + Soldier.getCost() + ")\n" +
							   "      A is the number of archers(cost $" + Archer.getCost() + ")\n" + 
							   "  and C is the number of cavalrymen(cost $" + Cavalry.getCost() + ").\n" +
							   "(maximum total cost $" + MAX_ARMY_COST + ")");
			String inputString = keyboard.readLine();
			
			// Parse the input into the numbers for the three types with validation
			String[] inputArray = inputString.split(" ");
			
			if (inputArray.length != 3) {
				throw new Exception("Wrong number of fighter types");
			}
			
			S = Integer.parseInt(inputArray[0]);
			A = Integer.parseInt(inputArray[1]);
			C = Integer.parseInt(inputArray[2]);
			
			if (S < 0 || A < 0 || C < 0) {
				throw new Exception("Cannot have a negative number of fighters");
			}
			
			if ((S * Soldier.getCost() + A * Archer.getCost() + C * Cavalry.getCost()) > MAX_ARMY_COST) {
				throw new Exception("Cost too high");
			}
		} catch (Exception e) {
			System.out.println("Error: " + e + ". Please try again.");
			System.out.println();
			return chooseArmy(name, keyboard);
		}
		
		// Push the fighters to the stack
		for (int i = 1; i <= C; i++) {
			army.push(new Cavalry());
		}
		for (int i = 1; i <= A; i++) {
			army.push(new Archer());
		}
		for (int i = 1; i <= S; i++) {
			army.push(new Soldier());
		}
		
		return army;
	}
	
	
	/**
	 * Implements the game with combat in a gladiatorial way.
	 * 
	 * @param		player1 The stack containing the army chosen by Player 1
	 * @param		player2 The stack containing the army chosen by Player 2
	 * @returns		The winner (1 = Player 1 wins, 2 = Player 2 wins, 0 = Draw, -1 = An error occurred)
	 * @throws		Exception if there is an error in simulating the battle (handled)
	 * @complexity	Best and Worst case O(N)
	 *              where N is the number of rounds taken to empty one of the stacks
	 */
	public static int gladiatorialCombat(LinkedFighterStack player1, LinkedFighterStack player2) {
		Fighter fighter1;
		Fighter fighter2;
		
		while (!player1.isEmpty() && !player2.isEmpty()) {
			try {
				// Get the fighters for this round
				fighter1 = player1.pop();
				fighter2 = player2.pop();
				
				// Simulate attacks/defense
				if (fighter1.getSpeed() > fighter2.getSpeed()) {
					// Fighter1 attacks first
					attackDefend(fighter1, fighter2, false);
				} else if (fighter2.getSpeed() > fighter1.getSpeed()) {
					// Fighter2 attacks first
					attackDefend(fighter2, fighter1, false);
				} else {
					// Both fighters attack at the same time
					attackDefend(fighter1, fighter2, true);
				}
				
				// Grant experience
				if (fighter1.isAlive() && fighter2.isAlive()) {
					fighter1.loseLife(1);
					fighter2.loseLife(1);
				} else if (!fighter2.isAlive()) {
					fighter1.gainExperience(1);
				} else if (!fighter1.isAlive()) {
					fighter2.gainExperience(1);
				}
				
				// Push the surviving fighters back to the stacks
				if (fighter1.isAlive()) {
					player1.push(fighter1);
				}
				if (fighter2.isAlive()) {
					player2.push(fighter2);
				}
			} catch (Exception e) {
				System.out.println("Error: Problem simulating battle - " + e);
				return -1;
			}
		}
		
		// Return the result
		if (player1.isEmpty() && player2.isEmpty()) {
			return 0;
		}
		if (player1.isEmpty()) {
			return 2;
		}
		if (player2.isEmpty()) {
			return 1;
		}
		
		// If it's not one of those three cases, something's gone wrong
		return -1;
	}
	
	/**
	 * Simulates the attack and defence for a given round
	 * 
	 * @param		firstAttacker the attacker who fights first if the attacks are not simultaneous	
	 * @param		secondAttacker the attacker who fights second if the attacks are not simultaneous	
	 * @param		simultaneous whether the attacks are simultaneous - if this is set to true, the order  
	 * 					of firstAttacker and secondAttacker is ignored
	 * @pre			firstAttacker and secondAttacker contain actual Fighters
	 * @post		firstAttacker and secondAttacker have been updated with the damage they receive this round
	 * @complexity  best and worth case O(1).
	 */
	public static void attackDefend(Fighter firstAttacker, Fighter secondAttacker, boolean simultaneous) {
		if (simultaneous) {
			// Both attack simultaneously
			secondAttacker.defend(firstAttacker.attackDamage());
			firstAttacker.defend(secondAttacker.attackDamage());
		} else {
			// firstAttacker attacks secondAttacker
			secondAttacker.defend(firstAttacker.attackDamage());
			
			// If secondAttacker is still alive, they fight back
			if (secondAttacker.isAlive()) {
				firstAttacker.defend(secondAttacker.attackDamage());
			}
		}
	}
}
